/*
 * Copyright 2007-2008  Lukasz Laszko lukaszlaszko@gmail.com
 * 
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and this
 * permission notice and warranty disclaimer appear in supporting
 * documentation, and that the name of the author not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * 
 * The author disclaim all warranties with regard to this
 * software, including all implied warranties of merchantability
 * and fitness.  In no event shall the author be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether
 * in an action of contract, negligence or other tortious action,
 * arising out of or in connection with the use or performance of
 * this software. 
 */
package org.pg.biomedics.asr.concurrent;

import java.io.File;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.net.URL;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioInputStream;

import org.apache.log4j.Logger;
import org.apache.commons.pool.ObjectPool;

import edu.cmu.sphinx.result.Lattice;
import edu.cmu.sphinx.result.LatticeOptimizer;

import org.pg.biomedics.asr.model.Task;
import org.pg.biomedics.asr.model.Result;
import org.pg.biomedics.asr.model.code.ResultTypeCodes;
import org.pg.biomedics.asr.model.code.TaskTypeCodes;
import org.pg.biomedics.asr.mpeg7.DocumentFactory;
import org.pg.biomedics.asr.mpeg7.SpokenContentDocument;

/**
 * ASR callable task. This class encapsulates logic for ASR task and 
 * is executed on top of thread pool. 
 * @author Lukasz Laszko
 */
public class VoiceEngineCallable implements Callable<Result>
{
    // <editor-fold desc="Fields">
    
    private Logger logger = Logger.getLogger(VoiceEngineCallable.class);
    
    private Task task;
    private Result result;
    private ObjectPool voiceEnginesPool;
    private List listeners = new ArrayList();
    
    // </editor-fold>
    
    // <editor-fold desc="Constructors">
    
    /**
     * Creates a new instance of ASR callable task for given task and 
     * voice engines pool
     * @param task task to execute
     * @param enginesPool pool of voice engines
     */
    public VoiceEngineCallable(Task task, ObjectPool voiceEnginesPool)
    {
        this.task = task;
        this.voiceEnginesPool = voiceEnginesPool;
    }
    
    // </editor-fold>
    
    // <editor-fold desc="Methods">
    
    public Result call() throws Exception
    {
        invokeExecutingListeners();
        
        VoiceEngine voiceEngine = null;
        try
        {
            voiceEngine = (VoiceEngine)this.voiceEnginesPool.borrowObject();
            
            URL audioUrl = new File(task.getMediaLocation()).toURI().toURL();
            AudioInputStream stream = AudioSystem.getAudioInputStream(audioUrl);
            
            voiceEngine.getReader().setInputStream(stream, audioUrl.toString());
            edu.cmu.sphinx.result.Result asrResult = voiceEngine.getRecognizer().recognize();
            
            this.result = createResult(asrResult);
        }
        catch (Throwable th)
        {
            logger.error("ASR task call failed, critical error!", th);
            this.result = createErrorResult(th);
        }
        finally
        {
            if (voiceEngine != null)
            {
                this.voiceEnginesPool.returnObject(voiceEngine);
                voiceEngine = null;
            }                       
        }    
        
        invokeExecutedListners();
        return this.result;
    }
    
    /**
     * Adds task call listner to call listeners queue
     * @param listener listner instance
     */
    public void addTaskCallListener(TaskCallListener listener)
    {
        synchronized(this.listeners)
        {
            if (listener != null)
                this.listeners.add(listener);
        }
    }
    
    /**
     * Removes listener from task queue listeners
     * @param listener listener instance
     */
    public void removeTaskCallListener(TaskCallListener listener)
    {
        synchronized(this.listeners)
        {
            if (listener != null)
                this.listeners.remove(listener);
        }
    }
    
    // <editor-fold>
    
    // <editor-fold desc="Support methods">
    
    private Result createResult(edu.cmu.sphinx.result.Result asrResult)
    {
        Result result = new Result();
        result.setTaskId(this.task.getId());
        
        // only transacriptions are supported now !!!
        if (this.task.getTypeCode().equals(TaskTypeCodes.TRANSCRIPTION))
        {
            result.setTypeCode(ResultTypeCodes.TRANSCRIPTION);
            result.setContent(asrResult.getBestResultNoFiller());
        }
        else if (this.task.getTypeCode().equals(TaskTypeCodes.LATTICE))
        {
            Lattice lattice = new Lattice(asrResult);
            LatticeOptimizer optimizer = new LatticeOptimizer(lattice);
            optimizer.optimize();
                        
            result.setTypeCode(ResultTypeCodes.LATTICE);
            result.setContent(getLatticeDump(lattice));
        }
        else if (this.task.getTypeCode().equals(TaskTypeCodes.MPEG7))
        {                 
            result.setTypeCode(ResultTypeCodes.MPEG7);
            result.setContent(getMpeg7DocumentContent(asrResult));
        }
        else 
        {
            result.setTypeCode(ResultTypeCodes.ERROR);
            result.setContent("Multi mode jobs not supported");
        }
        
        return result;
    }
    
    private Result createErrorResult(Throwable th)
    {
        Result result = new Result();
        result.setTaskId(this.task.getId());
        result.setTypeCode(ResultTypeCodes.ERROR);
        result.setContent(th.getMessage());
        
        return result;
    }
    
    private String getLatticeDump(Lattice lattice)
    {
        File tempFile = null;
        try                
        {  
            tempFile = File.createTempFile("lattice", "LAT");            
            lattice.dump(tempFile.getAbsolutePath());
            
            BufferedReader in = new BufferedReader(new InputStreamReader(tempFile.toURL().openStream()));
            StringBuilder latticeDump = new StringBuilder();
            String line;
            while ((line = in.readLine()) != null)
            {
                latticeDump.append(line);
            }
            in.close();
            
            return latticeDump.toString();
        }
        catch (IOException ex)
        {
            return "Error during Lattice dump";
        }
        finally
        {
            if (tempFile != null)
            {
                tempFile.delete();
            }
        }
    }
    
    private String getMpeg7DocumentContent(edu.cmu.sphinx.result.Result asrResult)
    {
        try
        {
            DocumentFactory documentFactory = new DocumentFactory();
            SpokenContentDocument document = documentFactory.createDocument(asrResult);
            
            return document.toXml();
        }
        catch (Exception ex)
        {
            return "Error during MPEG-7 SDC document extraction";
        }
    }
    
    private void invokeExecutingListeners()
    {            
        synchronized(this.listeners)
        {            
            for (int i = 0; i < this.listeners.size(); i++)
            {
                try
                {
                    TaskCallListener listener = (TaskCallListener)this.listeners.get(i);
                    listener.callExecuting(this.task);
                }
                catch (Throwable th)
                {
                    this.logger.error("Can't invoke executing listners for task " + this.task.getUniqueId());
                }
            }            
        }
    }
    
    private void invokeExecutedListners()
    {
        synchronized(this.listeners)
        {            
            for (int i = 0; i < this.listeners.size(); i++)
            {
                try
                {
                    TaskCallListener listener = (TaskCallListener)this.listeners.get(i);
                    listener.callExecuted(this.task, this.result);
                }
                catch (Throwable th)
                {
                    this.logger.error("Can't invoke executed listners for task " + this.task.getUniqueId());
                }
            }            
        }
    }
    
    // </editor-fold>
}
